import BattleMove from './move'
import BattleCalc from './calc'
import BattleCache from './cache'
import { isMyRoom } from '@/ayaka'
import BattleVisual from './visual'
import FlagController, { FlagPrefix } from '../flag'
import { isNearEdge } from '@/utils/position'

/**
 * 战斗核心，负责战斗的核心逻辑处理，包括选取目标，计算伤害，确定攻击或逃跑等
 */
export default class BattleCore {
    /**
     * 中立建筑
     */
    public static neutralStructures = new Set(['road', 'keeperLair', 'container'])

    /**
     * 蓝球贴近小队时造成的伤害倍数
     */
    public static rangeNearDamageRate: { [members: number]: number } = {
        1: 1,
        2: 2,
        3: 2.4,
        4: 2.8,
    }

    /**
     * 执行战斗任务
     */
    public static exec(flag: Flag) {
        // 更新状态
        BattleCore.updateStatus(flag)
        // 选择目标
        BattleCore.chooseTargets(flag)
        // 自动攻击
        BattleCore.autoAttack(flag)
        // 添加避让
        BattleCore.addAvoidObjs(flag)
        // 绘制一些信息
        BattleVisual.drawTargets(flag)
        BattleVisual.drawAvoidObjs(flag)
        // 仅用于调试
        if (Game.flags['team_stop'] && Game.flags['team_stop'].color === COLOR_RED) return
        // 集合
        if (!BattleMove.gatherCreeps(flag)) {
            return
        }
        // 小队移动
        BattleMove.teamMove(flag)
    }

    /**
     * 更新小队状态
     */
    public static updateStatus(flag: Flag) {
        if (!flag.memory.rush) {
            // 2 tick 内能奶住
            if (BattleCore.canHealInNTick(flag, 2)) {
                if (flag.memory.status === 'sleep' && Game.time % 7) return
                flag.memory.status = 'attack'
            }
            // 1 tick 内能奶住
            else if (BattleCore.canHealInNTick(flag, 1)) {
                flag.memory.status = 'avoid'
            }
            // 不能奶住
            else {
                flag.memory.status = 'flee'
            }
        } else {
            flag.memory.status = 'attack'
        }

        // 实际奶
        BattleCore.canHealInNTick(flag, 0)
        // 计算破防伤害
        flag._max_damage = BattleCore.maxBreakDamage(flag)

        BattleVisual.drawTeamStatus(flag)
        BattleVisual.drawCreepHealNeed(flag)
    }

    /**
     * n tick 内能否奶住，算伤策略有问题，但是先不管了
     * @returns true 表示能奶住，false 表示不能奶住
     *
     * TODO: 优化算伤策略，同时考虑会陷入沼泽的情况
     */
    public static canHealInNTick(flag: Flag, tick: number) {
        const creeps = flag._creeps

        // 搜索 n tick 后会攻击到自己的敌人
        creeps.forEach((creep) => {
            // 三格的蓝球敌人
            creep._ra_enemys = creep.room.find(FIND_HOSTILE_CREEPS).filter((e) => {
                return (
                    e.pos.inRangeTo(creep.pos, 3 + tick) &&
                    BattleCalc.hasBodyPart(e, RANGED_ATTACK) &&
                    BattleMove.touchableNTickInRange(e, creep.pos, tick, 3)
                )
            })

            // 一格的蓝球或者红球敌人
            creep._ra_atk_enemys = creep.room.find(FIND_HOSTILE_CREEPS).filter((e) => {
                return (
                    e.pos.inRangeTo(creep.pos, 1 + tick) &&
                    (BattleCalc.hasBodyPart(e, RANGED_ATTACK) || BattleCalc.hasBodyPart(e, ATTACK)) &&
                    BattleMove.touchableNTickInRange(e, creep.pos, tick, 1)
                )
            })

            // 被集火的伤害，即假设周围的爬都打到自己的伤害
            let maxCreepDamage = 0
            creep._ra_enemys.forEach((e) => {
                if (e._ra_damage === undefined) {
                    // rangedAttack 虽然是 3 格远，但是伤害和 1 格一样，同时不考虑敌人受伤（可能随时可能奶回来）
                    e._ra_damage = BattleCalc.calcRangeDamage(e, 1, false, false)
                }
                maxCreepDamage += e._ra_damage
            })
            creep._ra_atk_enemys.forEach((e) => {
                if (e._atk_damage === undefined) {
                    e._atk_damage = BattleCalc.calcAttackDamage(e, 1, false)
                }
                // 自己也有红球的话，打别人会造成反伤
                maxCreepDamage += e._atk_damage * (BattleCalc.hasBodyPart(creep, ATTACK) ? 2 : 1)
                // TODO: 一格以内的蓝球，对面肯定 mass，需要根据小队人数算出更大的伤害
                // maxCreepDamage += (e._ra_damage || 0) * BattleCore.rangeNearDamageRate[creeps.length]
            })

            // 塔伤分布图
            const towerDamageMap = isMyRoom(creep.room)
                ? BattleCache.emptyRoomArray
                : BattleCache.getTowerDamageMap(creep.room.name)
            // 集火伤害
            creep._fired_damage = maxCreepDamage + towerDamageMap.get(creep.pos.x, creep.pos.y)
            // 调试
            if (flag.memory._virtual_damage) {
                creep._fired_damage = flag.memory._virtual_damage
            }
        })

        let totalHeal = 0
        creeps.forEach((creep) => {
            if (creep._heal_power === undefined) {
                // 当前 tick 每个爬的真实治疗量
                creep._heal_power = BattleCalc.calcHealDamage(creep, 1, false, true)
            }
            totalHeal += creep._heal_power
        })

        creeps.forEach((creep) => {
            // 下一 tick 每个爬回满血需要的治疗量
            creep._need_heal_power = BattleCalc.calcDamageNeedHeal(creep, creep._fired_damage!, true)
            creep._heal_need = creep._need_heal_power
            // 当前 tick 对爬的破防伤害
            creep._break_damage = creep._break_damage ?? BattleCalc.calcHealHoldRealDamage(creep, totalHeal, true)
            // 不给治疗的话下一 tick 的虚拟血量
            creep._virtual_hits = creep.hitsMax - creep._need_heal_power
        })

        // 当前 tick 奶周围的爬（包括自己）
        creeps.forEach((creep) => {
            if (!creep._heal_power) return

            const needHealCreeps = creeps.filter((other) => other._need_heal_power! > 0 && creep.pos.isNearTo(other))
            if (!needHealCreeps.length) return

            // 找到周围破防伤害最小的爬
            const hurtCreeps = needHealCreeps.filter((c) => c.hits < c.hitsMax)
            let maxNeedHealCreep =
                hurtCreeps.length >= 1 &&
                hurtCreeps.reduce((pre, cur) => {
                    return pre._break_damage! < cur._break_damage! ? pre : cur
                })
            if (!maxNeedHealCreep) {
                // 找到周围需要奶量最大的爬
                maxNeedHealCreep = needHealCreeps.reduce((pre, cur) => {
                    return pre._need_heal_power! > cur._need_heal_power! ? pre : cur
                })
            }

            // tick 为 0 时才是真实情况，其他情况只是模拟未来的，而治疗需要根据真实情况
            if (tick === 0) {
                creep.heal(maxNeedHealCreep)
            }

            maxNeedHealCreep._need_heal_power! -= creep._heal_power!
            maxNeedHealCreep._virtual_hits! = Math.min(
                maxNeedHealCreep._virtual_hits! + creep._heal_power!,
                creep.hitsMax,
            )
        })

        if (creeps.every((creep) => !creep._fired_damage)) {
            return true
        }

        if (creeps.some((creep) => creep._virtual_hits! < 0 || creep.hits < creep.hitsMax * 0.6)) {
            // 第一 tick 就寄啦
            return false
        }

        // 下一 tick
        // 总奶量
        const totalNextTickHeal = creeps.reduce(
            (pre, cur) => pre + BattleCalc.calcHealDamage(cur, 1, false, cur._virtual_hits),
            0,
        )

        // 检查每个爬能否 hold 住
        if (
            creeps.find(
                (creep) =>
                    BattleCalc.calcHealHoldRealDamage(creep, totalNextTickHeal, creep._virtual_hits!) <
                    creep._fired_damage!,
            )
        ) {
            return false
        }

        return true
    }

    /**
     * 小队能承受的最大伤害，大于该值一定会破防，低于该值可能破防
     */
    public static maxBreakDamage(flag: Flag) {
        const creeps = flag._creeps
        // 取所有爬中最小的破防伤害
        return creeps.reduce((pre, cur) => (pre < cur._break_damage! ? pre : cur._break_damage), Infinity)
    }

    /**
     * 获取攻击目标，赶路的时候用，打爬以及低血量的建筑
     */
    public static getAttackTargets(creep: Creep) {
        const hostileCreeps = creep.room.find(FIND_HOSTILE_CREEPS)
        const hostileStructures = creep.room
            .find(FIND_HOSTILE_STRUCTURES)
            .filter((s) => s.hits && s.hits <= BattleMove.structHitLimit)
        return [...hostileCreeps, ...hostileStructures].filter((s) => creep.pos.inRangeTo(s, 3))
    }

    /**
     * 自动攻击
     */
    public static autoAttack(flag: Flag) {
        let targets = flag._targets?.filter((s) => 'hits' in s) as (Creep | Structure)[]
        flag._attackTargets = []

        const clearPathCache = (creep: Creep) => {
            if (isNearEdge(creep.pos, 2)) return false
            BattleMove.clearPathCache(flag)
            return true
        }

        const creepsInFiveRange = (flag._attackable_creeps || []).filter(
            (c) => c.pos.inRangeTo(flag._creeps[0], 5) && !c.pos.coverRampart(),
        )
        targets = Array.from(new Set([...targets, ...creepsInFiveRange]))

        flag._creeps.forEach((creep) => {
            // 不是目标房间靠近就打
            if (creep.room.name !== flag.pos.roomName) {
                targets = BattleCore.getAttackTargets(creep)!
            }

            if (!targets?.length) return

            let expand = false
            let targetsInThreeRange = targets.filter((s) => creep.pos.inRangeTo(s, 3))
            if (!targetsInThreeRange.length) {
                targetsInThreeRange = [
                    ...(flag._attackable_creeps || []),
                    ...(flag._attackable_structures || []),
                ].filter((s) => creep.pos.inRangeTo(s, 3))
                expand = true
            }
            if (!targetsInThreeRange.length) {
                // 删除寻路缓存
                clearPathCache(creep)
                return
            }

            if (creep.getActiveBodyparts(RANGED_ATTACK)) {
                // 先找爬中生命最小的，再找建筑最近的
                let target: Creep | Structure
                const targetCreeps: Creep[] = []
                const targetStructures: Structure[] = []
                targetsInThreeRange.forEach((s) => {
                    if (s instanceof Creep) {
                        targetCreeps.push(s)
                    } else {
                        targetStructures.push(s)
                        s._range = creep.pos.getRangeTo(s)
                    }
                })

                if (targetCreeps.length && !flag.memory.forceStructure) {
                    target = targetCreeps.reduce((pre, cur) => (pre.hits < cur.hits ? pre : cur))
                } else {
                    target = targetStructures.reduce((pre, cur) => (pre._range! < cur._range! ? pre : cur))
                }

                // 与目标相邻则有 owner 属性
                if (creep.pos.isNearTo(target) && 'owner' in target) {
                    creep.rangedMassAttack()
                } else {
                    creep.rangedAttack(target)
                }
                flag._attackTargets?.push(target)
            }

            const attack = creep.getActiveBodyparts(ATTACK)
            if (attack) {
                // 如果是爬且对面 attack 更多就不打了
                let targetsInOneRange = targetsInThreeRange.filter((s) => creep.pos.inRangeTo(s, 1))
                if (!expand && !targetsInOneRange.length) {
                    targetsInOneRange = [
                        ...(flag._attackable_creeps || []),
                        ...(flag._attackable_structures || []),
                    ].filter((s) => creep.pos.inRangeTo(s, 1))
                }

                const attackableTargets = flag.memory.rush
                    ? targetsInOneRange
                    : targetsInOneRange.filter((s) => {
                          return !(s instanceof Creep && s.getActiveBodyparts(ATTACK) > attack)
                      })
                if (!attackableTargets.length) {
                    // 删除寻路缓存
                    clearPathCache(creep)
                    return
                }

                const target = attackableTargets.reduce((pre, cur) => (pre.hits < cur.hits ? pre : cur))
                creep.attack(target)
                flag._attackTargets?.push(target)
            }

            if (creep.getActiveBodyparts(WORK)) {
                let targetInOneRange = targetsInThreeRange
                    .filter((s) => creep.pos.inRangeTo(s, 1))
                    .filter((s) => {
                        return s instanceof Structure
                    }) as Structure[]
                if (!expand && !targetInOneRange.length) {
                    targetInOneRange = [...(flag._attackable_structures || [])].filter((s) =>
                        creep.pos.inRangeTo(s, 1),
                    ) as Structure[]
                }
                if (!targetInOneRange.length) {
                    // 删除寻路缓存
                    clearPathCache(creep)
                    return
                }

                const target = targetInOneRange.reduce((pre, cur) => (pre.hits < cur.hits ? pre : cur))
                creep.dismantle(target)
                flag._attackTargets?.push(target)
            }
        })
    }

    /**
     * 选取寻路目标
     */
    public static chooseTargets(flag: Flag) {
        const room = flag._room
        // 旗帜是黄色表示专注建筑
        const isFocusStructure = flag.color === COLOR_YELLOW || flag.memory.hasWork || flag.memory.forceStructure
        // 是否先打爬
        const preferCreep =
            flag.memory.preferCreep ||
            FlagController.getFlagsByPrefixAndRoom(FlagPrefix.preferCreep, flag.memory.belong).find(
                (f) => f.pos.roomName === flag.pos.roomName,
            )
        // 是否优先打旗帜下面的东西
        const preferFlag = flag.color === COLOR_RED

        const result: (Creep | Structure | Flag)[] = []
        const creep = flag._creeps.find((c) => c.room.name === flag.pos.roomName)

        if (room && creep) {
            if (preferFlag) {
                const targets = [...flag.pos.lookFor(LOOK_STRUCTURES)].filter((s) => s.structureType !== STRUCTURE_ROAD)
                if (targets.length) {
                    flag._targets = [targets[0]]
                    flag._attackable_creeps = room.find(FIND_HOSTILE_CREEPS)
                    const structures = room
                        .find(FIND_HOSTILE_STRUCTURES)
                        .filter((s) => s.hits && !s.my && !BattleCore.neutralStructures.has(s.structureType))
                    flag._attackable_structures = structures
                    return
                }
            }

            let canAttackStructures: Structure[] = []
            let canAttackCreeps: Creep[] = []

            const getCanAttackStructures = () => {
                const structures = room
                    .find(FIND_HOSTILE_STRUCTURES)
                    .filter((s) => s.hits && !s.my && !BattleCore.neutralStructures.has(s.structureType))
                flag._attackable_structures = structures
                canAttackStructures = BattleCache.getStructuresInFloodFill(creep, structures)
            }

            const getCanAttackCreeps = () => {
                const creeps = room.find(FIND_HOSTILE_CREEPS)
                flag._attackable_creeps = creeps
                canAttackCreeps = BattleCache.getCreepsInFloodFill(creep, creeps)
            }

            if (isFocusStructure) {
                getCanAttackStructures()
            } else if (preferCreep) {
                getCanAttackCreeps()
                if (!canAttackCreeps.length) {
                    getCanAttackStructures()
                }
            } else {
                getCanAttackCreeps()
                getCanAttackStructures()
            }

            result.push(...canAttackStructures, ...canAttackCreeps)
        }

        if (result.length === 0) {
            result.push(flag)
        }
        flag._targets = result
    }

    /**
     * 添加需要避让的物体
     */
    public static addAvoidObjs(flag: Flag, range = 1) {
        if (flag.memory.status === 'avoid' || flag.memory.status === 'flee') {
            flag.memory.lastAvoidTime = Game.time
        }

        if (!flag.memory.lastAvoidTime || flag.memory.lastAvoidTime + 3 < Game.time) {
            return
        }

        const roomsSet = new Set(flag._creeps.map((creep) => creep.room))
        if (flag.room) roomsSet.add(flag.room)
        const rooms = Array.from(roomsSet)
        const hostiles: Creep[] = []
        rooms.forEach((room) => {
            hostiles.push(
                ...room.find(FIND_HOSTILE_CREEPS).filter((e) => {
                    if (!e.getActiveBodyparts(RANGED_ATTACK) && !e.getActiveBodyparts(ATTACK)) {
                        return
                    }

                    return flag._creeps.every((c) => c.pos.crossRoomGetRangeTo(e.pos) <= range + 5)
                }),
            )
        })

        flag._avoidObjs = hostiles.map((e) => ({
            pos: e.pos,
            range: range + (e.getActiveBodyparts(RANGED_ATTACK) ? 3 : 1),
        }))
    }
}
